#region File Header
//----------------------------------------------------------------
// Copyright (C) 2002 Stefan Kyntchev
// All rights reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY 
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT 
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR 
// FITNESS FOR A PARTICULAR PURPOSE.
//----------------------------------------------------------------
#endregion File Header

namespace AirWeb.ApplicationTools
{
	using System;
	using System.IO;
	using System.Collections;
	using System.Collections.Specialized;
	using System.Configuration;
	using System.Web;
	using AirWeb.Security;
	using AirWeb.Security.FileSecurity;
	using AirWeb.ApplicationTools.Common;
	using AirWeb.ApplicationTools.Image;
	using AirWeb.DirectoryMgr;


	// A delegate type for hooking up status notifications.
	public delegate void StatusEventHandler(object sender, DirHelperStatusEvent e);

	/// <summary>
	/// This class encapsulates an event that the Directory Helper sends
	/// while generating pictures
	/// </summary>
	public class DirHelperStatusEvent : EventArgs
	{
		public enum EVT_TYPES
		{
			READY_TO_GENERATE,
			GENERATING,
			DONE_GENERATING,
			READY_TO_DELETE,
			DELETING,
			DONE_DELETING
		};

		EVT_TYPES m_Type;
		string m_Status;
		int m_Number;

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="i_type">Event Type</param>
		/// <param name="i_status">Status message</param>
		/// <param name="i_number">Associated Number</param>
		public DirHelperStatusEvent(EVT_TYPES i_type, string i_status, int i_number)
		{
			m_Type = i_type;
			m_Status = i_status;
			m_Number = i_number;
		}

		public EVT_TYPES Type
		{
			get { return m_Type; }
		}

		public string SatusMsg
		{
			get { return m_Status; }
		}

		public int Number
		{
			get { return m_Number; }
		}
	}

	
	/// <summary>
	///    Summary description for DirectoryHelper.
	/// </summary>
	/// 	
	public class DirectoryHelper
	{
		public enum FolderType
		{
			ss,
			tn,
			org
		};

		private HttpContext m_page = null;
		protected static String m_tn_dir = "tn";
		protected static String m_ss_dir = "ss";

		//This event will fire each time Directory Helper wants
		//to notify about what is it doing
		public event StatusEventHandler StatusEvent;

		public DirectoryHelper()
		{
			m_page = HttpContext.Current;
		}

		/// <summary>
		///    Creates a directory path to the thumbnails directory. The returned path is a virtual path.
		/// </summary>
		public String CreateThumbnailPath(String i_directory)
		{
			return AppPath + "Pics/" + m_tn_dir + "/" + i_directory;	
		}
		
		/// <summary>
		///    Creates a directory path to the slides directory. The returned path is a virtual path.
		/// </summary>
		public String CreateSlidePath(String i_directory)
		{
			return AppPath + "Pics/" + m_ss_dir + "/" + i_directory;
		}

		/// <summary>
		///    Creates a directory path to the directory that contains picture originals. The returned path is a virtual path.
		/// </summary>
		public String CreateOriginalPath(String i_directory)
		{
			return OriginalsVirtualRoot + i_directory;	
		}

		/// <summary>
		///    Converts thumbnails virtual path to originals virtual path
		/// </summary>
		public String TN2OG( String i_path )
		{
			String src = CreateThumbnailPath("");
			String rpl = OriginalsVirtualRoot;
			return Replace(i_path,src,rpl);
		}

		/// <summary>
		///    Converts slides virtual path to originals virtual path
		/// </summary>
		public String SL2OG( String i_path )
		{
			String src = CreateSlidePath("");
			String rpl = OriginalsVirtualRoot;
			return Replace(i_path,src,rpl);
		}

		/// <summary>
		///    Converts originals virtual path to thumbnails virtual path
		/// </summary>
		public String OG2TN( String i_path )
		{
			String rpl = CreateThumbnailPath("");
			String src = OriginalsVirtualRoot;
			return Replace(i_path,src,rpl);
		}

		/// <summary>
		///    Converts originals virtual path to slide virtual path
		/// </summary>
		public String OG2SL(String i_path)
		{
			String rpl = CreateSlidePath("");
			String src = OriginalsVirtualRoot;
			return Replace(i_path, src, rpl);
		}

		/// <summary>
		///    Converts thumbnails virtual path to slide virtual path
		/// </summary>
		public String TN2SL( String i_path )
		{
			String src = CreateThumbnailPath("");
			String rpl = CreateSlidePath("");
			return Replace(i_path,src,rpl);
		}

		/// <summary>
		///    Returns a virtual path that represents a physical path.
		/// </summary>
		public String ReverseMapPath(String i_absolute, String i_virtual)
		{
			string basePath = m_page.Request.MapPath(i_virtual);
			string fn = i_absolute;			
			string newPath = i_virtual + i_absolute.Substring(basePath.Length);
			
			newPath = newPath.Replace(@"\",@"/");
			return newPath.Replace(@"//",@"/");
		}

		/// <summary>
		///    Replaces all occurences of i_old with i_new in i_str.
		/// </summary>
		public static String Replace( String i_str, String i_old, String i_new )
		{
			String rv = i_str;
			int start;
			do
			{
				start = rv.IndexOf(i_old);
				if(start != -1)
				{
					rv = rv.Substring(0,start) + i_new + rv.Substring(start + i_old.Length);
				}
				
			} while (start != -1); 
			
			return rv;
		}

		/// <summary>
		///    Given a physical path finds which subdirectory was most recently modified. Returns the date and time
		///    that directory was modified. It traverses 10 levels deep.
		/// </summary>
		public static DateTime GetLastModifiedTime(String i_directory)
		{
			DirectoryLister Lister = new DirectoryLister();
			FileComparer c = new FileComparer();
			c.compareBy = CompareBy.ModificationDate;
			c.Sort = SortOrder.Ascending;
			Lister.Comparer = c;
			
			ArrayList directories = Lister.ListDirectories(i_directory,10);

			if(directories.Count == 0)
			{
				throw new ArgumentException("Invalid Directory supplied or the directory is empty!",i_directory);
			}
			DirectoryInfo d = (DirectoryInfo)directories[0];
			return d.LastWriteTime;
		}

		/// <summary>
		/// Returns the virtual path to the application directory. Set in web.config
		/// </summary>
		public string AppPath
		{
			get
			{
				string p = m_page.Request.ApplicationPath;
				if(!p.EndsWith("/"))
					p += "/";
				return p;
			}
		}

		/// <summary>
		/// Returns the virtual path to the original pictures. Set in web.config
		/// </summary>
		public string OriginalsVirtualRoot 
		{
			get
			{
				NameValueCollection config = (NameValueCollection)
					ConfigurationSettings.GetConfig("flashBack/directories");			
				return config["ORIGINALS_VIRTUAL_PATH"].Replace(@"\","/");
			}
		}

		/// <summary>
		/// Returns the physical path to the original pictures.
		/// </summary>
		public string OriginalsPhysicalRoot
		{
			get
			{
				return m_page.Request.MapPath(OriginalsVirtualRoot);	
			}
		}


		/// <summary>
		/// The location of the public folder
		/// </summary>
		public string PublicFolderVirtualRoot
		{
			get
			{
				NameValueCollection config = (NameValueCollection)
					ConfigurationSettings.GetConfig("flashBack/directories");
			
				string p = config["PUBLIC_FOLDER_VIRTUAL_PATH"];
				
				if( p == null)
					return string.Empty;
				else
					return p.Replace(@"\","/");
			}
		}

		/// <summary>
		/// Gets the list of pictures given the path to originals.Generates the pictures if necesary
		/// </summary>
		/// <param name="i_path">The offset path to originals </param>
		/// <param name="i_ft">The type of folder to create</param>
		/// <returns></returns>
		public ArrayList GetPictureList(string i_path, FolderType i_ft, User i_user)
		{
			// Get the path to the originals
			String org = CreateOriginalPath(i_path);
			org = m_page.Request.MapPath(org);

			DirectoryInfo dorg = new DirectoryInfo(org);
			if(!dorg.Exists)
				return new ArrayList();

			// Make sure that the destination directory exist
			DirectoryInfo ddest = SyncDirectory(dorg, i_ft);
	
			// Simply check if there are difference in the number
			// of files in both directories. If yes - sync the content
			// This also ensures that the old files are deleted
			if(dorg.GetFiles().Length != ddest.GetFiles().Length)
				SyncDirContents(dorg,ddest,i_ft);

			DirectoryLister d = new DirectoryLister();

			// Now get the requested files
			return GetPictureList(d.ListFiles(org,0),i_ft,i_user);
		}
		
		/// <summary>
		/// Checks and if neccessary generates thumbnails or slide pictures given
		/// a specified array of originals
		/// </summary>
		/// <param name="i_path">Array of originals</param>
		/// <param name="i_ft">Wether to generate thumbnails or slides</param>
		/// <returns>list of FileInfo objects</returns>
		public ArrayList GetPictureList(ArrayList i_files,FolderType i_ft,User i_user)
		{
			// First limit the file list to only files the user can see
			i_files = FilterRestrictedItems(i_user,i_files);

			if(i_ft == FolderType.org)
			{
				return i_files;  // we are done;
			}

			// Now for each file in the list make sure that the appropriate thumbnail or 
			// slide show file exist
			ArrayList l = new ArrayList();
			foreach (FileInfo o in i_files)
			{
				//if(CommonHelper.IsMovie(o.FullName))  // if the file is a movie - jut add it
				//{
				//	l.Add(o);
				//	continue;
				//}

				DirectoryInfo dest = SyncDirectory(o.Directory, i_ft);
				FileInfo f = new FileInfo(dest.FullName + "\\" + o.Name);
				if(!f.Exists || CompareFiles(o,f) == false)
				{
					// Sync only if needed
					f = SyncFile(o,dest, i_ft);
				}

				if(f != null)
					l.Add(f);
			}

			return l;
		}

		protected DirectoryInfo SyncDirectory(DirectoryInfo i_org,FolderType i_ft)
		{
			// Get the path to the destination
			String dest;

			string offset = "";
			if(i_org.FullName.Length > OriginalsPhysicalRoot.Length)
				offset = i_org.FullName.Substring(OriginalsPhysicalRoot.Length);

			if(i_ft == FolderType.ss)
				dest = CreateSlidePath(offset);
			else
				dest = CreateThumbnailPath(offset);

			// Convert to physical path
			dest = m_page.Request.MapPath(dest);

			// Create the directory if doesn't exist
			DirectoryInfo ddest = new DirectoryInfo(dest);
			if(!ddest.Exists)
			{
				Directory.CreateDirectory(dest);
				ddest = new DirectoryInfo(dest);
			}

			return ddest;
		}

		/// <summary>
		/// Sychronizes the content of two picture directories
		/// </summary>
		/// <param name="i_org">The directory that contains the originals</param>
		/// <param name="i_dest">The destination directory for generated items</param>
		/// <param name="i_ft">Wether to generate slides or thumbnails</param>
		/// <returns>number of actually generated items</returns>
		private ArrayList SyncDirContents(DirectoryInfo i_org,
									 DirectoryInfo i_dest,
									 FolderType i_ft)
		{
			ArrayList matched = new ArrayList();
			ArrayList unmatched = new ArrayList();
			ArrayList fordeletion = new ArrayList();
			ArrayList rv = new ArrayList();

			DirHelperStatusEvent e;

			int NumGenerated = 0;
			FileInfo[] origs = i_org.GetFiles();
			FileInfo[] dests = i_dest.GetFiles();

			foreach (FileInfo o in origs)
			{
				bool found = false;

				foreach (FileInfo d in dests)
				{
					found = CompareFiles(d,o);
					if(found)
					{
						matched.Add(d);
						break;
					}
				}

				// Get the files that don't have thumbnails
				if(!found)
				{
					unmatched.Add(o);
				}
			}

			//Send an event that pictures that need generating are found
			if(unmatched.Count > 0)
			{
				e = new DirHelperStatusEvent(
							DirHelperStatusEvent.EVT_TYPES.READY_TO_GENERATE,
							i_ft.ToString(),
							unmatched.Count);
				OnStatus(e);
			}

			//Generate pictures and send events about that
			foreach(FileInfo o in unmatched)
			{
				FileInfo f = SyncFile(o,i_dest,i_ft);
				if(f != null)
				{
					NumGenerated++;
					matched.Add(f);

					//Send an event that a picture was generated
					e = new DirHelperStatusEvent(
						DirHelperStatusEvent.EVT_TYPES.GENERATING,
						i_ft.ToString(),
						NumGenerated);
					OnStatus(e);
				}
			}

			//Send an event that we are done generating
			if(unmatched.Count > 0)
			{
				e = new DirHelperStatusEvent(
					DirHelperStatusEvent.EVT_TYPES.DONE_GENERATING,
					i_ft.ToString(),
					NumGenerated);
				OnStatus(e);
			}

			// delete old files that don't have originals
			foreach (FileInfo d in dests)
			{
				bool found = false;

				foreach (FileInfo m in matched)
				{
					found = CompareFiles(d,m);
					if(found)
					{
						break;
					}
				}

				if(!found)
				{
					fordeletion.Add(d);
				}

			}

			//Send an event that we are about to delete some pictures
			if(fordeletion.Count > 0)
			{
				e = new DirHelperStatusEvent(
					DirHelperStatusEvent.EVT_TYPES.READY_TO_DELETE,
					i_ft.ToString(),
					fordeletion.Count);
				OnStatus(e);
			}

			//Do the actual deletion
			int deleting = 0;
			foreach(FileInfo d in fordeletion)
			{
				d.Delete();

				//Send an event that a picture was geleted
				e = new DirHelperStatusEvent(
					DirHelperStatusEvent.EVT_TYPES.DELETING,
					i_ft.ToString(),
					++deleting);
				OnStatus(e);				
			}

			//Send an event that we are done generating
			if(fordeletion.Count > 0)
			{
				e = new DirHelperStatusEvent(
					DirHelperStatusEvent.EVT_TYPES.DONE_DELETING,
					i_ft.ToString(),
					NumGenerated);
				OnStatus(e);
			}

			// Finaly return the file in the destination
			dests = i_dest.GetFiles();
			foreach (FileInfo d in dests)
			{
				rv.Add(d);
			}

			return rv;
		}

		protected FileInfo SyncFile(FileInfo i_org, DirectoryInfo i_dest, FolderType i_ft)
		{
			FileInfo f;

			try
			{
				String destpath = i_dest.FullName + "\\" + i_org.Name;
				ThumbNailGenerator g = new ThumbNailGenerator(i_org.FullName);
				if(i_ft == FolderType.ss)
					g.Generate(700,destpath);
				else
					g.Generate(100,destpath);

				f = new FileInfo(destpath);
			}
			catch
			{
				// Just ignore. This is most probably a movie or other unknown picture format
				return null;
			}

			return f;
		}

		protected bool CompareFiles(FileInfo lhs, FileInfo rhs)
		{
			if(lhs.Name == rhs.Name)
			{
				return true;
			}

			return false;
		}

		/// <summary>
		/// Publishes a change status event
		/// </summary>
		/// <param name="e">What changed</param>
		protected void OnStatus(DirHelperStatusEvent e)
		{
			if (StatusEvent != null)
				StatusEvent(this, e);
		}
		public int GetNumPictures()
		{
			DirectoryLister l  = new DirectoryLister();
			int nf =  (l.ListFiles(OriginalsPhysicalRoot,10)).Count;
			return nf;
		}

		public int GetNumFolders()
		{
			DirectoryLister l  = new DirectoryLister();
			int nd = (l.ListDirectories(OriginalsPhysicalRoot,10)).Count;
			return nd;
		}

		public int GetNumMovies()
		{
			return 0;
		}

		public string UrlEncodeFilePath(string i_string)
		{
			string r =  i_string.Replace("\\","/");
			r =  r.Replace("'","%27");
			return r;
		}

		/// <summary>
		/// Perfoms a security check on a list of file items and removes the items that do
		/// not pass the check. Uses the active user to do the verification
		/// </summary>
		/// <param name="i_user">The security mamanger to perform the check</param>
		/// <param name="i_list">List of input items</param>
		/// <returns>Filtered list after the security check</returns>
		public ArrayList FilterRestrictedItems(User i_user, ArrayList i_list)
		{
			ArrayList ret = new ArrayList();

			/*if(m_page.User.Identity.IsAuthenticated == false)
				throw new ApplicationException("You need an authenticated user to perform this request");*/

			foreach (FileSystemInfo i in i_list)
			{
				if(CanUserAccess(i_user,i))
				{
					ret.Add(i);
				}
			}

			return ret;
		}

		public bool CanUserAccess(User i_user, FileSystemInfo i_info)
		{
			string vfilename = ReverseMapPath(i_info.FullName,OriginalsVirtualRoot);

			FilePathResource fpr = new FilePathResource(vfilename);

			if(SecurityMgr.Instance.CanRead(i_user,fpr) == true )
				return true;
			else
				return false;
		}
	}
}
